package com.ccp.dev.workflow.service.bpm.impl;
import com.ccp.dev.core.basic.api.ISysOrg;
import com.ccp.dev.core.basic.engine.GroovyScriptEngine;
import com.ccp.dev.core.basic.util.ContextUtil;
import com.ccp.dev.core.util.BeanUtils;
import com.ccp.dev.core.util.StringPool;
import com.ccp.dev.core.util.StringUtil;
import com.ccp.dev.system.model.SysOrg;
import com.ccp.dev.workflow.bpmconst.BpmConst;
import com.ccp.dev.workflow.dao.BpmProStatusDao;
import com.ccp.dev.workflow.model.BpmNodeScript;
import com.ccp.dev.workflow.model.TaskOpinion;
import com.ccp.dev.workflow.model.bpm.BpmNodeSign;
import com.ccp.dev.workflow.model.bpm.ProcessCmd;
import com.ccp.dev.workflow.service.BpmNodeScriptService;
import com.ccp.dev.workflow.service.BpmService;
import com.ccp.dev.workflow.service.TaskOpinionService;
import com.ccp.dev.workflow.service.bpm.BpmNodeSignService;
import com.ccp.dev.workflow.service.bpm.ISignComplete;
import com.ccp.dev.workflow.service.bpm.TaskSignDataService;
import com.ccp.dev.workflow.service.bpm.thread.TaskThreadService;
import com.google.common.graph.Traverser;
import org.flowable.bpmn.model.Activity;
import org.flowable.bpmn.model.MultiInstanceLoopCharacteristics;
import org.flowable.engine.delegate.DelegateExecution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.net.ContentHandler;
import java.time.Period;
import java.util.*;

/**
 * ClassDescribe:
 * 根据会签规则判定流程如何结束。<br>
 * <pre>
 * 系统提供的变量：
 * nrOfInstances：总的实例个数。
 * nrOfActiveInstances：当前活动的实例个数。
 * nrOfCompletedInstances：完成的实例个数。
 * 取得变量的方法：
 * execution.getVariable("变量名称");
 *
 * 使用方法：
 * 1.spring app-beans.xml配置文件中配置如下：
 * &lt;bean id="signComplete" class="com.casic.platform.service.bpm.impl.SignComplete">&lt;/bean>
 * 2.流程定义配置如下：
 * &lt;completionCondition >${signComplete.isComplete(execution) }&lt;/completionCondition>
 * </pre>
 *
 * @author :wangcheng
 * Date: 2019-09-04
 */
@Service
public class SignComplete implements ISignComplete {

    private static final Logger LOGGER = LoggerFactory.getLogger(SignComplete.class);

    @Resource
    private BpmProStatusDao bpmProStatusDao;
    @Resource
    private TaskOpinionService taskOpinionService;
    @Resource
    private BpmNodeScriptService bpmNodeScriptService;
    @Resource
    private GroovyScriptEngine groovyScriptEngine;
    @Resource
    private BpmNodeSignService bpmNodeSignService;
    @Resource
    private TaskSignDataService taskSignDataService;


    @Override
    public boolean isComplete(DelegateExecution execution) {

        LOGGER.debug("enter the SignComplete isComplete method..");

        String nodeId = execution.getCurrentActivityId();
        String processInstanceId = execution.getProcessInstanceId();
        String processDefinitionId = execution.getProcessDefinitionId();

        boolean isComplete = false;
        String signResult = "";

        // 取得会签设置的规则
        BpmNodeSign bpmNodeSign = bpmNodeSignService.getByProcDefIdAndNodeId(processDefinitionId, nodeId);
        // 完成会签的次数
        Integer completeCounter = (Integer) execution.getVariable("nrOfCompletedInstances");
        MultiInstanceLoopCharacteristics loopCharacteristics = ((Activity) execution.getCurrentFlowElement()).getLoopCharacteristics();
        boolean sequential = loopCharacteristics.isSequential();

        // 总循环次数
        Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances");

        ProcessCmd processCmd=TaskThreadService.getProcessCmd();

        // 获取审批状态
        short approvalStatus = processCmd.getVoteAgree().shortValue();

        String orgId = "";
        SysOrg sysOrg = (SysOrg) ContextUtil.getCurrentOrg();
        if (BeanUtils.isNotEmpty(sysOrg)) {
            orgId = sysOrg.getOrgId();
        }

        // 驳回的话直接完成
        if (BpmConst.TASK_BACK_TOSTART.equals(processCmd.isBack()) || BpmConst.TASK_BACK.equals(processCmd.isBack())) {
            isComplete = true;
        } else if (approvalStatus == 5 || approvalStatus == 6) {
            // 特权处理 直接通过（5 同意，6 反对）
            isComplete = true;
            if (approvalStatus == 5) {
                signResult = SIGN_RESULT_PASS;
            }else {
                signResult = SIGN_RESULT_REFUSE;
            }
        }else {
            // 一票决策，在calcResult前调用
            // 获取一票角色规则
            boolean isOneVote = bpmNodeSignService.checkNodeSignPrivilege(processDefinitionId, nodeId, BpmNodeSignService.BpmNodePrivilegeType.ALLOW_ONE_VOTE, ContextUtil.getCurrentUserId(), orgId);
            if (isOneVote) {
                if (!execution.hasVariable(StringPool.PREFIX_SIGN_RESULT + nodeId)) {
                    // 已有决策的话，本次决策不起作用
                    execution.setVariable(StringPool.PREFIX_SIGN_RESULT + nodeId,approvalStatus);
                }
            }

            // 一票决策结果
            Short oneVoteResult = null;
            if (execution.hasVariable(StringPool.PREFIX_SIGN_RESULT + nodeId)) {
                oneVoteResult = (Short) execution.getVariable(StringPool.PREFIX_SIGN_RESULT+ nodeId);
            }

            // 计算投票结果
            VoteResult voteResult = calcResult(bpmNodeSign, processInstanceId, nodeId, completeCounter, nrOfInstances, oneVoteResult);
            signResult = voteResult.getSignResult();
            isComplete = voteResult.getIsComplete();
        }

        /*
         * 会签完成做的动作。
         * 1.删除会签的流程变量。
         * 2.将会签数据更新为完成。
         * 3.设置会签结果变量。
         * 4.更新会签节点结果。
         * 5.清除会签用户。
         */
        if (isComplete) {
            // 删除会签变量 assign, loopCounter
//            bpmService.delLoopAssigneeVars(execution.getId());
            // 将会前数据更新为完成
            taskSignDataService.batchUpdateCompleted(processInstanceId, nodeId);

            Short status=null;
            if(BpmConst.TASK_BACK_TOSTART.equals(processCmd.isBack())||BpmConst.TASK_BACK.equals(processCmd.isBack())){
                status=processCmd.getVoteAgree();
                if(TaskOpinion.STATUS_RECOVER_TOSTART.equals(status)||TaskOpinion.STATUS_RECOVER.equals(status)){
                    signResult=SIGN_RESULT_RECOVER;
                }else if(TaskOpinion.STATUS_REJECT_TOSTART.equals(status)){
                    signResult=SIGN_RESULT_TOSTART;
                }else if(TaskOpinion.STATUS_REJECT.equals(status)){
                    signResult=SIGN_RESULT_BACK;
                }else{
                    signResult="UNKNOW_ACTION";
                }
            }else{
                //更新会签节点的状态。
                status=TaskOpinion.STATUS_PASSED;
                if(signResult.equals(SIGN_RESULT_REFUSE)){
                    status=TaskOpinion.STATUS_NOT_PASSED;
                }
            }

            LOGGER.debug("set the sign result + " + signResult);
            // 设置会签结果
            execution.setVariable("signResult_" + nodeId , signResult);
            String resultSign= "resultOfSign_"+nodeId;
            if( execution.hasVariable(resultSign)){
                execution.removeVariable(resultSign);
            }

            //更新会签节点的状态。
            Map<String, Object> params = new HashMap<>();
            params.put("procInstId", processInstanceId);
            params.put("nodeId", nodeId);
            params.put("status", status);
            bpmProStatusDao.updStatus(params);

            //修改任务状态。
            updOption(execution,status);

            // 如果流程为串行会签节点删除会签人员变量。
            // 串行会签人员首先从流程变量中获取。
            if (sequential) {
                String varName=nodeId +"_" +BpmConst.SIGN_USERIDS;
                execution.removeVariable(varName);
            }
        }

        exeEventScript(execution, processDefinitionId, nodeId, isComplete);

        return isComplete;
    }

    /**
     * 执行事件脚本
     * @param execution
     * @param procDefId
     * @param nodeId
     * @param isComplete
     */
    private void exeEventScript(DelegateExecution execution, String procDefId, String nodeId, boolean isComplete) {
        BpmNodeScript signNodeScript = bpmNodeScriptService.getScriptByType(nodeId, procDefId, BpmNodeScript.SCRIPT_TYPE_5);
        if (signNodeScript == null) {
            return;
        }
        Map<String, Object> vars = execution.getVariables();
        vars.put("execution", execution);
        vars.put("isCompleted", isComplete);
        groovyScriptEngine.execute(signNodeScript.getScript(), vars);
    }

    /**
     * 会签完成时，修改会签的状态。
     * <pre>
     * 并行会签需要修改状态。
     * 如果当会签完成时，会签的任务还有没有完成的那么修改意见的状态。
     * 1.驳回
     * 		修改成驳回取消
     * 2.通过取消
     * 3.否决取消
     * 4.撤销取消
     * @param execution
     * @param signStatus
     */
    private void updOption(DelegateExecution execution, Short signStatus) {
        String nodeId = execution.getCurrentActivityId();
        String procInstId = execution.getProcessInstanceId();
        MultiInstanceLoopCharacteristics loopCharacteristics = ((Activity) execution.getCurrentFlowElement()).getLoopCharacteristics();
        boolean sequential = loopCharacteristics.isSequential();
        if (sequential) {
            return;
        }

        Short status=getStatus(signStatus);

        List<TaskOpinion> list=taskOpinionService.queryByProcInstIdTaskKeyStatus(procInstId,nodeId,TaskOpinion.STATUS_CHECKING);
        for(TaskOpinion taskOpinion:list){
            taskOpinion.setCheckStatus(status);
            taskOpinion.setEndTime(new Date());
//            Long duration = calendarAssignService.getRealWorkTime(taskOpinion.getStartTime(), taskOpinion.getEndTime(), taskOpinion.getExeUserId());
//            taskOpinion.setDurTime(duration);
            taskOpinionService.update(taskOpinion);
        }

    }

    /**
     * 获取审批状态
     * @param signResult
     * @return
     */
    private Short getStatus(Short signResult) {
        ProcessCmd cmd=TaskThreadService.getProcessCmd();
        Short status=TaskOpinion.STATUS_PASS_CANCEL;
        /*
         * 0，正常跳转。
         * 1，驳回
         * 2，驳回到发起人。
         */
        int isBack=cmd.isBack();
        boolean isRecover=cmd.isRecover();
        switch(isBack){
            //正常
            case 0:
                if(TaskOpinion.STATUS_PASSED.equals(signResult)){
                    status=TaskOpinion.STATUS_PASS_CANCEL;
                }
                else{
                    status=TaskOpinion.STATUS_REFUSE_CANCEL;
                }
                break;
            //驳回（追回)
            case 1:
                //驳回（追回)到发起人。
            case 2:
                if(isRecover){
                    status=TaskOpinion.STATUS_REVOKED_CANCEL;
                }
                else{
                    status=TaskOpinion.STATUS_BACK_CANCEL;
                }
                break;
            default:
                break;
        }
        return status;
    }

    /**
     * 根据会签规则计算投票结果。
     * <pre>
     * 1.如果会签规则为空，那么需要所有的人同意通过会签，否则不通过。
     * 2.否则按照规则计算投票结果。
     * </pre>
     * @param bpmNodeSign 会签规则
     * @param processInstanceId 流程实例ID
     * @param nodeId 节点id名称
     * @param completeCounter 循环次数
     * @param nrOfInstances 总的会签次数
     * @param oneVoteResult 一票决策结果
     * @return
     */
    private VoteResult calcResult(BpmNodeSign bpmNodeSign, String processInstanceId, String nodeId, Integer completeCounter, Integer nrOfInstances, Short oneVoteResult) {
        VoteResult voteResult = new VoteResult();

        // 投同意票数
        Integer agreeAmount = taskSignDataService.getAgreeVoteCount(processInstanceId, nodeId);
        // 投反对票数
        Integer refuseAmount = taskSignDataService.getRefuseVoteCount(processInstanceId, nodeId);
        // 在没有设置会签规则的情况下
        if (bpmNodeSign == null) {
            voteResult = getResultNoRule(oneVoteResult, refuseAmount, agreeAmount, nrOfInstances);
            return voteResult;
        }

        // 存在会签规则
        voteResult = getResultByRule(bpmNodeSign, oneVoteResult, agreeAmount, refuseAmount, completeCounter, nrOfInstances);
        return voteResult;
    }

    /**
     *
     * @param bpmNodeSign 会签规则
     * @param oneVoteResult  一票决策结果
     * @param agreeAmount 同意票数
     * @param refuseAmount 反对票数
     * @param completeCounter 完成的实例数
     * @param nrOfInstances 实例总数
     * @return
     */
    private VoteResult getResultByRule(BpmNodeSign bpmNodeSign, Short oneVoteResult, Integer agreeAmount, Integer refuseAmount, Integer completeCounter, Integer nrOfInstances) {
        VoteResult voteResult = new VoteResult();
        // 符合规则即终止投票过程
        // 否则全部投票直到结束
        boolean isDirect = BpmNodeSign.FLOW_MODE_DIRECT.equals(bpmNodeSign.getFlowMode());
        // 存在特权的情况
        if (oneVoteResult != null) {
            String result = (oneVoteResult == 1) ? SIGN_RESULT_PASS : SIGN_RESULT_REFUSE;
            // 直接完成的情况
            if (isDirect) {
                voteResult = new VoteResult(result, true);
            } else if (completeCounter.equals(nrOfInstances)) {
                // 完成全部投票根据特权判定结果
                voteResult = new VoteResult(result, true);
            }
            return voteResult;
        }else{
            // 没有特权
            if (BpmNodeSign.VOTE_TYPE_PERCENT.equals(bpmNodeSign.getVoteType())) {
                voteResult = getResultByPercent(bpmNodeSign, agreeAmount, refuseAmount, nrOfInstances, completeCounter);
            }else{
                voteResult = getResultByVotes(bpmNodeSign, agreeAmount, refuseAmount, nrOfInstances, completeCounter);
            }

        }

        return voteResult;
    }

    /**
     * 根据投票数金酸投票结果
     * @param bpmNodeSign 会签规则
     * @param agreeAmount 同意票数
     * @param refuseAmount 反对票数
     * @param nrOfInstances 总实例数
     * @param completeCounter 完成数量
     * @return
     */
    private VoteResult getResultByVotes(BpmNodeSign bpmNodeSign, Integer agreeAmount, Integer refuseAmount, Integer nrOfInstances, Integer completeCounter) {
        boolean isComplete = nrOfInstances.equals(completeCounter);
        VoteResult voteResult = new VoteResult();
        String result = "";
        boolean isPass = false;
        boolean isDirect = bpmNodeSign.getFlowMode() == 1;
        float percents = 0;

        // 按同意票数决定
        if (BpmNodeSign.DECIDE_TYPE_PASS.equals(bpmNodeSign.getDecideType())) {
            // 同意票符合条件
            if(agreeAmount>=bpmNodeSign.getVoteAmount()){
                result=SIGN_RESULT_PASS;
                isPass=true;
            }
            else{
                result=SIGN_RESULT_REFUSE;
            }
        }else {
            //按反对票数进行决定
            if(refuseAmount>=bpmNodeSign.getVoteAmount()){
                result=SIGN_RESULT_REFUSE;
                isPass=true;
            }
            else{
                result=SIGN_RESULT_PASS;
            }
        }
        //直接过
        if(isDirect && isPass){
            voteResult=new VoteResult(result,true);
        }
        else if(isComplete){
            voteResult=new VoteResult(result,true);
        }
        return voteResult;
    }

    /**
     * 根据投票百分比计算投票结果。
     * @param bpmNodeSign 会签规则
     * @param agreeAmount 同意票数
     * @param refuseAmount 反对票数
     * @param nrOfInstances 总实例数
     * @param completeCounter 完成数量
     * @return
     */
    private VoteResult getResultByPercent(BpmNodeSign bpmNodeSign, Integer agreeAmount, Integer refuseAmount, Integer nrOfInstances, Integer completeCounter) {
        boolean isComplete = nrOfInstances.equals(completeCounter);
        VoteResult voteResult = new VoteResult();
        String result = "";
        boolean isPass = false;
        boolean isDirect = bpmNodeSign.getFlowMode() == 1;
        float percents = 0;
        // 按同意票数进行决定
        if (BpmNodeSign.DECIDE_TYPE_PASS.equals(bpmNodeSign.getDecideType())) {
            percents = ((float)agreeAmount) / nrOfInstances;
            // 投票同意符合条件
            if (percents * 100 >= bpmNodeSign.getVoteAmount()) {
                result = SIGN_RESULT_PASS;
                isPass = true;
            }else{
                result = SIGN_RESULT_REFUSE;
            }
        }else{
            percents = ((float)refuseAmount) / nrOfInstances;
            // 投票同意符合条件
            if (percents * 100 >= bpmNodeSign.getVoteAmount()) {
                result = SIGN_RESULT_REFUSE;
                isPass = true;
            }else{
                result = SIGN_RESULT_PASS;
            }
        }

        // 直接过
        if (isDirect && isPass) {
            voteResult=new VoteResult(result,true);
        }else if(isComplete){
            voteResult=new VoteResult(result,true);
        }
        return voteResult;
    }

    /**
     * 没有会签规则时计算会签结果。
     * @param oneVoteResult 一票决策结果
     * @param refuseAmount 反对票数
     * @param agreeAmount 同意票数
     * @param nrOfInstances 实例个数
     * @return
     */
    private VoteResult getResultNoRule(Short oneVoteResult, Integer refuseAmount, Integer agreeAmount, Integer nrOfInstances) {
        VoteResult voteResult = new VoteResult();
        // 有特权
        if (oneVoteResult != null) {
            if (oneVoteResult == 1) {
                voteResult.setSignResult(SIGN_RESULT_PASS);
            } else {
                voteResult.setSignResult(SIGN_RESULT_REFUSE);
            }
            voteResult.setIsComplete(true);
        } else {
            // 没有特权， 只要一票反对
            if (refuseAmount > 0) {
                voteResult.setSignResult(SIGN_RESULT_REFUSE);
                voteResult.setIsComplete(true);
            } else if (agreeAmount.equals(nrOfInstances)) {
                voteResult.setSignResult(SIGN_RESULT_PASS);
                voteResult.setIsComplete(true);

            }
        }
        return voteResult;
    }

    /**
     * 投票结果。
     * <pre>
     * 1.是否会签完成。
     * 2.会签结果，pass，refuse。
     * </pre>
     */
    class VoteResult{
        /**
         * 会签结果
         */
        private String signResult ="";
        /**
         * 是否完成
         */
        private boolean isComplete=false;

        public VoteResult(){}

        public VoteResult(String signResult,boolean isComplate){
            this.signResult=signResult;
            this.isComplete=isComplate;
        }

        public String getSignResult() {
            return signResult;
        }
        public void setSignResult(String signResult) {
            this.signResult = signResult;
        }
        public boolean getIsComplete() {
            return isComplete;
        }
        public void setIsComplete(boolean isComplete) {
            this.isComplete = isComplete;
        }
    }
}
